home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / ver_cont / cvs-1.8 / cvs-1 / cvs-1.8.1 / src / logmsg.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-06  |  13.4 KB  |  522 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * Copyright (c) 1989-1992, Brian Berliner
  4.  * 
  5.  * You may distribute under the terms of the GNU General Public License as
  6.  * specified in the README file that comes with the CVS 1.4 kit.
  7.  */
  8.  
  9. #include "cvs.h"
  10. #include "getline.h"
  11.  
  12. static int find_type PROTO((Node * p, void *closure));
  13. static int fmt_proc PROTO((Node * p, void *closure));
  14. static int logfile_write PROTO((char *repository, char *filter, char *title,
  15.               char *message, char *revision, FILE * logfp,
  16.               List * changes));
  17. static int rcsinfo_proc PROTO((char *repository, char *template));
  18. static int title_proc PROTO((Node * p, void *closure));
  19. static int update_logfile_proc PROTO((char *repository, char *filter));
  20. static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
  21. static int editinfo_proc PROTO((char *repository, char *template));
  22.  
  23. static FILE *fp;
  24. static char *str_list;
  25. static char *editinfo_editor;
  26. static Ctype type;
  27.  
  28. /*
  29.  * Puts a standard header on the output which is either being prepared for an
  30.  * editor session, or being sent to a logfile program.  The modified, added,
  31.  * and removed files are included (if any) and formatted to look pretty. */
  32. static char *prefix;
  33. static int col;
  34. static void
  35. setup_tmpfile (xfp, xprefix, changes)
  36.     FILE *xfp;
  37.     char *xprefix;
  38.     List *changes;
  39. {
  40.     /* set up statics */
  41.     fp = xfp;
  42.     prefix = xprefix;
  43.  
  44.     type = T_MODIFIED;
  45.     if (walklist (changes, find_type, NULL) != 0)
  46.     {
  47.     (void) fprintf (fp, "%sModified Files:\n", prefix);
  48.     (void) fprintf (fp, "%s\t", prefix);
  49.     col = 8;
  50.     (void) walklist (changes, fmt_proc, NULL);
  51.     (void) fprintf (fp, "\n");
  52.     }
  53.     type = T_ADDED;
  54.     if (walklist (changes, find_type, NULL) != 0)
  55.     {
  56.     (void) fprintf (fp, "%sAdded Files:\n", prefix);
  57.     (void) fprintf (fp, "%s\t", prefix);
  58.     col = 8;
  59.     (void) walklist (changes, fmt_proc, NULL);
  60.     (void) fprintf (fp, "\n");
  61.     }
  62.     type = T_REMOVED;
  63.     if (walklist (changes, find_type, NULL) != 0)
  64.     {
  65.     (void) fprintf (fp, "%sRemoved Files:\n", prefix);
  66.     (void) fprintf (fp, "%s\t", prefix);
  67.     col = 8;
  68.     (void) walklist (changes, fmt_proc, NULL);
  69.     (void) fprintf (fp, "\n");
  70.     }
  71. }
  72.  
  73. /*
  74.  * Looks for nodes of a specified type and returns 1 if found
  75.  */
  76. static int
  77. find_type (p, closure)
  78.     Node *p;
  79.     void *closure;
  80. {
  81.     if (p->data == (char *) type)
  82.     return (1);
  83.     else
  84.     return (0);
  85. }
  86.  
  87. /*
  88.  * Breaks the files list into reasonable sized lines to avoid line wrap...
  89.  * all in the name of pretty output.  It only works on nodes whose types
  90.  * match the one we're looking for
  91.  */
  92. static int
  93. fmt_proc (p, closure)
  94.     Node *p;
  95.     void *closure;
  96. {
  97.     if (p->data == (char *) type)
  98.     {
  99.     if ((col + (int) strlen (p->key)) > 70)
  100.     {
  101.         (void) fprintf (fp, "\n%s\t", prefix);
  102.         col = 8;
  103.     }
  104.     (void) fprintf (fp, "%s ", p->key);
  105.     col += strlen (p->key) + 1;
  106.     }
  107.     return (0);
  108. }
  109.  
  110. /*
  111.  * Builds a temporary file using setup_tmpfile() and invokes the user's
  112.  * editor on the file.  The header garbage in the resultant file is then
  113.  * stripped and the log message is stored in the "message" argument.
  114.  * 
  115.  * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
  116.  * is NULL, use the CVSADM_TEMPLATE file instead.
  117.  */
  118. void
  119. do_editor (dir, messagep, repository, changes)
  120.     char *dir;
  121.     char **messagep;
  122.     char *repository;
  123.     List *changes;
  124. {
  125.     static int reuse_log_message = 0;
  126.     char *line;
  127.     int line_length;
  128.     size_t line_chars_allocated;
  129.     char fname[L_tmpnam+1];
  130.     struct stat pre_stbuf, post_stbuf;
  131.     int retcode = 0;
  132.     char *p;
  133.  
  134.     if (noexec || reuse_log_message)
  135.     return;
  136.  
  137.     /* Abort creation of temp file if no editor is defined */
  138.     if (strcmp (Editor, "") == 0 && !editinfo_editor)
  139.     error(1, 0, "no editor defined, must use -e or -m");
  140.  
  141.     /* Create a temporary file */
  142.     (void) tmpnam (fname);
  143.   again:
  144.     if ((fp = fopen (fname, "w+")) == NULL)
  145.     error (1, 0, "cannot create temporary file %s", fname);
  146.  
  147.     if (*messagep)
  148.     {
  149.     (void) fprintf (fp, "%s", *messagep);
  150.  
  151.     if ((*messagep)[strlen (*messagep) - 1] != '\n')
  152.         (void) fprintf (fp, "\n");
  153.     }
  154.     else
  155.     (void) fprintf (fp, "\n");
  156.  
  157.     if (repository != NULL)
  158.     /* tack templates on if necessary */
  159.     (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
  160.     else
  161.     {
  162.     FILE *tfp;
  163.     char buf[1024];
  164.     char *p;
  165.     size_t n;
  166.     size_t nwrite;
  167.  
  168.     /* Why "b"?  */
  169.     tfp = fopen (CVSADM_TEMPLATE, "rb");
  170.     if (tfp == NULL)
  171.     {
  172.         if (!existence_error (errno))
  173.         error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
  174.     }
  175.     else
  176.     {
  177.         while (!feof (tfp))
  178.         {
  179.         n = fread (buf, 1, sizeof buf, tfp);
  180.         nwrite = n;
  181.         p = buf;
  182.         while (nwrite > 0)
  183.         {
  184.             n = fwrite (p, 1, nwrite, fp);
  185.             nwrite -= n;
  186.             p += n;
  187.         }
  188.         if (ferror (tfp))
  189.             error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
  190.         }
  191.         if (fclose (tfp) < 0)
  192.         error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
  193.     }
  194.     }
  195.  
  196.     (void) fprintf (fp,
  197.   "%s----------------------------------------------------------------------\n",
  198.             CVSEDITPREFIX);
  199.     (void) fprintf (fp,
  200.   "%sEnter Log.  Lines beginning with `%s' are removed automatically\n%s\n",
  201.             CVSEDITPREFIX, CVSEDITPREFIX, CVSEDITPREFIX);
  202.     if (dir != NULL && *dir)
  203.     (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
  204.             dir, CVSEDITPREFIX);
  205.     if (changes != NULL)
  206.     setup_tmpfile (fp, CVSEDITPREFIX, changes);
  207.     (void) fprintf (fp,
  208.   "%s----------------------------------------------------------------------\n",
  209.             CVSEDITPREFIX);
  210.  
  211.     /* finish off the temp file */
  212.     if (fclose (fp) == EOF)
  213.         error (1, errno, "%s", fname);
  214.     if (stat (fname, &pre_stbuf) == -1)
  215.     pre_stbuf.st_mtime = 0;
  216.  
  217.     if (editinfo_editor)
  218.     free (editinfo_editor);
  219.     editinfo_editor = (char *) NULL;
  220.     if (repository != NULL)
  221.     (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
  222.  
  223.     /* run the editor */
  224.     run_setup ("%s", editinfo_editor ? editinfo_editor : Editor);
  225.     run_arg (fname);
  226.     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
  227.                  RUN_NORMAL | RUN_SIGIGNORE)) != 0)
  228.     error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
  229.            editinfo_editor ? "Logfile verification failed" :
  230.            "warning: editor session failed");
  231.  
  232.     /* put the entire message back into the *messagep variable */
  233.  
  234.     fp = open_file (fname, "r");
  235.  
  236.     if (*messagep)
  237.     free (*messagep);
  238.  
  239.     if (stat (fname, &post_stbuf) != 0)
  240.         error (1, errno, "cannot find size of temp file %s", fname);
  241.  
  242.     if (post_stbuf.st_size == 0)
  243.     *messagep = NULL;
  244.     else
  245.     {
  246.     /* On NT, we might read less than st_size bytes, but we won't
  247.        read more.  So this works.  */
  248.     *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
  249.      *messagep[0] = '\0';
  250.     }
  251.  
  252.     line = NULL;
  253.     line_chars_allocated = 0;
  254.  
  255.     if (*messagep)
  256.     {
  257.     p = *messagep;
  258.     while (1)
  259.     {
  260.         line_length = getline (&line, &line_chars_allocated, fp);
  261.         if (line_length == -1)
  262.         {
  263.         if (ferror (fp))
  264.             error (0, errno, "warning: cannot read %s", fname);
  265.         break;
  266.         }
  267.         if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0)
  268.         continue;
  269.         (void) strcpy (p, line);
  270.         p += line_length;
  271.     }
  272.     }
  273.     if (fclose (fp) < 0)
  274.     error (0, errno, "warning: cannot close %s", fname);
  275.  
  276.     if (pre_stbuf.st_mtime == post_stbuf.st_mtime ||
  277.     *messagep == NULL ||
  278.     strcmp (*messagep, "\n") == 0)
  279.     {
  280.     for (;;)
  281.     {
  282.         (void) printf ("\nLog message unchanged or not specified\n");
  283.         (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
  284.         (void) printf ("Action: (continue) ");
  285.         (void) fflush (stdout);
  286.         line_length = getline (&line, &line_chars_allocated, stdin);
  287.         if (line_length <= 0
  288.             || *line == '\n' || *line == 'c' || *line == 'C')
  289.         break;
  290.         if (*line == 'a' || *line == 'A')
  291.         error (1, 0, "aborted by user");
  292.         if (*line == 'e' || *line == 'E')
  293.         goto again;
  294.         if (*line == '!')
  295.         {
  296.         reuse_log_message = 1;
  297.         break;
  298.         }
  299.         (void) printf ("Unknown input\n");
  300.     }
  301.     }
  302.     if (line)
  303.     free (line);
  304.     if (unlink_file (fname) < 0)
  305.     error (0, errno, "warning: cannot remove temp file %s", fname);
  306. }
  307.  
  308. /*
  309.  * callback proc for Parse_Info for rcsinfo templates this routine basically
  310.  * copies the matching template onto the end of the tempfile we are setting
  311.  * up
  312.  */
  313. /* ARGSUSED */
  314. static int
  315. rcsinfo_proc (repository, template)
  316.     char *repository;
  317.     char *template;
  318. {
  319.     static char *last_template;
  320.     FILE *tfp;
  321.  
  322.     /* nothing to do if the last one included is the same as this one */
  323.     if (last_template && strcmp (last_template, template) == 0)
  324.     return (0);
  325.     if (last_template)
  326.     free (last_template);
  327.     last_template = xstrdup (template);
  328.  
  329.     if ((tfp = fopen (template, "r")) != NULL)
  330.     {
  331.     char *line = NULL;
  332.     size_t line_chars_allocated = 0;
  333.  
  334.     while (getline (&line, &line_chars_allocated, tfp) >= 0)
  335.         (void) fputs (line, fp);
  336.     if (ferror (tfp))
  337.         error (0, errno, "warning: cannot read %s", template);
  338.     if (fclose (tfp) < 0)
  339.         error (0, errno, "warning: cannot close %s", template);
  340.     if (line)
  341.         free (line);
  342.     return (0);
  343.     }
  344.     else
  345.     {
  346.     error (0, errno, "Couldn't open rcsinfo template file %s", template);
  347.     return (1);
  348.     }
  349. }
  350.  
  351. /*
  352.  * Uses setup_tmpfile() to pass the updated message on directly to any
  353.  * logfile programs that have a regular expression match for the checked in
  354.  * directory in the source repository.  The log information is fed into the
  355.  * specified program as standard input.
  356.  */
  357. static char *title;
  358. static FILE *logfp;
  359. static char *message;
  360. static char *revision;
  361. static List *changes;
  362.  
  363. void
  364. Update_Logfile (repository, xmessage, xrevision, xlogfp, xchanges)
  365.     char *repository;
  366.     char *xmessage;
  367.     char *xrevision;
  368.     FILE *xlogfp;
  369.     List *xchanges;
  370. {
  371.     char *srepos;
  372.  
  373.     /* nothing to do if the list is empty */
  374.     if (xchanges == NULL || xchanges->list->next == xchanges->list)
  375.     return;
  376.  
  377.     /* set up static vars for update_logfile_proc */
  378.     message = xmessage;
  379.     revision = xrevision;
  380.     logfp = xlogfp;
  381.     changes = xchanges;
  382.  
  383.     /* figure out a good title string */
  384.     srepos = Short_Repository (repository);
  385.  
  386.     /* allocate a chunk of memory to hold the title string */
  387.     if (!str_list)
  388.     str_list = xmalloc (MAXLISTLEN);
  389.     str_list[0] = '\0';
  390.  
  391.     type = T_TITLE;
  392.     (void) walklist (changes, title_proc, NULL);
  393.     type = T_ADDED;
  394.     (void) walklist (changes, title_proc, NULL);
  395.     type = T_MODIFIED;
  396.     (void) walklist (changes, title_proc, NULL);
  397.     type = T_REMOVED;
  398.     (void) walklist (changes, title_proc, NULL);
  399.     title = xmalloc (strlen (srepos) + strlen (str_list) + 1 + 2); /* for 's */
  400.     (void) sprintf (title, "'%s%s'", srepos, str_list);
  401.  
  402.     /* to be nice, free up this chunk of memory */
  403.     free (str_list);
  404.     str_list = (char *) NULL;
  405.  
  406.     /* call Parse_Info to do the actual logfile updates */
  407.     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
  408.  
  409.     /* clean up */
  410.     free (title);
  411. }
  412.  
  413. /*
  414.  * callback proc to actually do the logfile write from Update_Logfile
  415.  */
  416. static int
  417. update_logfile_proc (repository, filter)
  418.     char *repository;
  419.     char *filter;
  420. {
  421.     return (logfile_write (repository, filter, title, message, revision,
  422.                logfp, changes));
  423. }
  424.  
  425. /*
  426.  * concatenate each name onto str_list
  427.  */
  428. static int
  429. title_proc (p, closure)
  430.     Node *p;
  431.     void *closure;
  432. {
  433.     if (p->data == (char *) type)
  434.     {
  435.     (void) strcat (str_list, " ");
  436.     (void) strcat (str_list, p->key);
  437.     }
  438.     return (0);
  439. }
  440.  
  441. /*
  442.  * Since some systems don't define this...
  443.  */
  444. #ifndef MAXHOSTNAMELEN
  445. #define    MAXHOSTNAMELEN    256
  446. #endif
  447.  
  448. /*
  449.  * Writes some stuff to the logfile "filter" and returns the status of the
  450.  * filter program.
  451.  */
  452. static int
  453. logfile_write (repository, filter, title, message, revision, logfp, changes)
  454.     char *repository;
  455.     char *filter;
  456.     char *title;
  457.     char *message;
  458.     char *revision;
  459.     FILE *logfp;
  460.     List *changes;
  461. {
  462.     char cwd[PATH_MAX];
  463.     FILE *pipefp;
  464.     char *prog = xmalloc (MAXPROGLEN);
  465.     char *cp;
  466.     int c;
  467.     int pipestatus;
  468.  
  469.     /*
  470.      * Only 1 %s argument is supported in the filter
  471.      */
  472.     (void) sprintf (prog, filter, title);
  473.     if ((pipefp = run_popen (prog, "w")) == NULL)
  474.     {
  475.     if (!noexec)
  476.         error (0, 0, "cannot write entry to log filter: %s", prog);
  477.     free (prog);
  478.     return (1);
  479.     }
  480.     (void) fprintf (pipefp, "Update of %s\n", repository);
  481.     (void) fprintf (pipefp, "In directory %s:%s\n\n", hostname,
  482.             ((cp = getwd (cwd)) != NULL) ? cp : cwd);
  483.     if (revision && *revision)
  484.     (void) fprintf (pipefp, "Revision/Branch: %s\n\n", revision);
  485.     setup_tmpfile (pipefp, "", changes);
  486.     (void) fprintf (pipefp, "Log Message:\n%s\n", message);
  487.     if (logfp != (FILE *) 0)
  488.     {
  489.     (void) fprintf (pipefp, "Status:\n");
  490.     rewind (logfp);
  491.     while ((c = getc (logfp)) != EOF)
  492.         (void) putc ((char) c, pipefp);
  493.     }
  494.     free (prog);
  495.     pipestatus = pclose (pipefp);
  496.     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
  497. }
  498.  
  499. /*
  500.  * We choose to use the *last* match within the editinfo file for this
  501.  * repository.  This allows us to have a global editinfo program for the
  502.  * root of some hierarchy, for example, and different ones within different
  503.  * sub-directories of the root (like a special checker for changes made to
  504.  * the "src" directory versus changes made to the "doc" or "test"
  505.  * directories.
  506.  */
  507. /* ARGSUSED */
  508. static int
  509. editinfo_proc(repository, editor)
  510.     char *repository;
  511.     char *editor;
  512. {
  513.     /* nothing to do if the last match is the same as this one */
  514.     if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
  515.     return (0);
  516.     if (editinfo_editor)
  517.     free (editinfo_editor);
  518.  
  519.     editinfo_editor = xstrdup (editor);
  520.     return (0);
  521. }
  522.